/**
 *  @fileOverview  cQuery is a Query style JavaScript Framework specially
 *  written for Chrome Ext, requires Chrome 18 or higher.
 *
 *  @author xiaody <xiaody09@163.com>
 *  @date 2012-05
 */

;(function() {
'use strict';

/** @const */
var _cQuery = window['cQuery'],
	_$ = window['$'];

var cQuery = function( selector, context ) {
	return new cQuery.fn.init( selector, context );
};

var slice = Array.prototype.slice,
	isArray = Array.isArray;

cQuery['fn'] = cQuery.prototype = {
	/**
	 * @constructor
	 */
	init : function( selector, context ) {
		selector = selector || document;

		if ( selector.nodeType ) {
			this[0] = selector;
			this.length = 1;
			return this;
		}

		if ( typeof selector === 'string' ) {
			if ( /^<(\w+)>$/.test( selector ) )
				return this.setArray( cQuery.makeArray( document.createElement( RegExp.$1 ) ) );
			this.setArray( cQuery( context ).find( selector ) );
			return this;
		}

		return this.setArray( cQuery.makeArray( selector ) );
	},

	show : function() {
		this.forEach( function( elem ) {
			elem.style.display = 'block';
		});
	},
	hide : function() {
		this.forEach( function( elem ) {
			elem.style.display = 'none';
		});
	},
	css : function( prop, value) {
		if ( arguments.length === 1 ) {
			if ( typeof prop === 'string' )
				return window.getComputedStyle( this[0], null ).getPropertyValue( prop );
			value = prop;
			this.forEach( function( elem ) {
				for ( var key in value )
					elem.style[key] = value[key];
			});
		}

		this.forEach( function( elem ) {
			elem.style[prop] = value;
		});

		return this;
	},

	/** @type {number} */
	length : 0,

	/**
	 * @param {Array.<*>} elems
	 */
	setArray : function( elems ) {
		this.length = 0;

		Array.prototype.push.apply( this, elems );

		return this;
	},
	toArray : function() {
		return slice.call( this, 0 );
	},

	/**
	 * @param {number} index
	 */
	eq : function( index ) {
		index = +index;
		if ( index < 0 )
			index += this.length;
		return cQuery( this[index] );
	},
	/**
	 * @param {number} index
	 */
	get : function( index ) {
		if ( !arguments.length )
			return this.toArray();

		index = +index;
		if ( index < 0 )
			index += this.length;
		return this[index];
	},

	children : function() {
		var allChidren = cQuery.map( this, function( parent ) {
			return cQuery.makeArray( parent.children );
		});

		return cQuery( allChidren );
	},
	childNodes : function() {
		var allChidren = cQuery.map( this, function( parent ) {
			return cQuery.makeArray( parent.childNodes );
		});

		return cQuery( allChidren );
	},
	siblings : function() {
		var allSiblings = cQuery.map( this, function( self ) {
			var ret = [], elem = self.parentNode.firstElementChild;
			do {
				if ( elem !== self )
					ret.push( elem );
			} while ( elem = elem.nextElementSibling );
			return ret;
		});

		return cQuery( allSiblings );
	},

	prop : function( name, val ) {
		'use strict';
		if ( arguments.length === 2 ) {
			this.forEach(function( elem ) {
				elem[name] = val;
			});
		} else {
			if ( typeof name === 'string' ) {
				return this[0][name];
			} else if ( typeof name === 'object' ) {
				val = name;
				this.forEach(function( elem ) {
					for ( var key in val ) {
						elem[key] = val[key];
					}
				});
			}
		}

		return this;
	},

	attr : function( name, val ) {
		'use strict';
		if ( arguments.length === 2 ) {
			this.forEach( function( elem ) {
				elem.setAttribute( name, val );
			});
		} else {
			if ( typeof name === 'string' ) {
				return this[0].getAttribute( name );
			} else if ( typeof name === 'object' ) {
				val = name;
				this.forEach( function( elem ) {
					for ( var key in val ) {
						elem.setAttribute( key, val[key] );
					}
				});
			}
		}

		return this;
	},
	/**
	 * @param {!string} name
	 */
	removeAttr : function( name ) {
		this.forEach(function( elem ) {
			elem.removeAttribute( name );
		});
		return this;
	},

	/**
	 * @param {!string} classNames
	 */
	addClass : function( classNames ) {
		'use strict';
		this.forEach(function( elem ) {
			if ( typeof classNames === 'string' ) {
				classNames = classNames.split( /\s+/ );
				classNames.forEach( function( cln ) {
					elem.classList.add( cln );
				});
			}
		});
		return this;
	},
	/**
	 * @param {!string} className
	 * @return {boolean}
	 */
	hasClass : function( className ) {
		return this[0].classList.contains( className );
	},
	/**
	 * @param {!string} className
	 */
	removeClass : function( className ) {
		this.forEach(function( elem ) {
			elem.classList.remove( className );
		});
		return this;
	},

	/**
	 * @param {!string} selector
	 */
	find : function( selector ) {
		var elems = cQuery.map( this, function( elem ) {
			return cQuery.find( selector, elem );
		});

		return cQuery( elems );
	},

	/**
	 * @param {function(..*)} callback
	 */
	forEach : function( callback ) {
		cQuery.forEach( this, callback );
		return this;
	},

	/**
	 * @param {*} elem
	 * @return {boolean}
	 */
	contains : function( elem ) {
		'use strict';
		for ( var i = 0, l = this.length; i < l; ++i ) {
			if ( this[i] === elem )
				return true;
		}
		return false;
	},

	/**
	 * @param {number|string=} val
	 */
	height : function( val ) {
		if ( arguments.length === 0 ) {
			return +window.getComputedStyle( this[0], null ).getPropertyValue( "height" ).match( /^\d+/ );
		} else {
			if ( /^\d+$/.test(val) )
				val += 'px';
			this.forEach( function( elem ) {
				elem.style.height = val;
			});
			return this;
		}
	},
	/**
	 * @return {Object.<string,number>}
	 */
	offset : function() {
		'use strict';
		var top = 0, left = 0, elem = this[0];
		do {
			top += elem.offsetTop;
			left += elem.offsetLeft;
		} while ( elem = elem.offsetParent );

		return {
			'top' : top,
			'left' : left
		};
	},

	append : function() {
		var parent = this[0];
		cQuery.makeArray( arguments ).forEach( function( children ) {
			if ( !children.length || children.nodeType === 3 )
				parent.appendChild( children );
			else
				children.forEach( function( child ) {
					if ( child instanceof HTMLElement === true || child.nodeType === 3 || child.nodeType === 11 )
						parent.appendChild( child );
				});
		});
		return this;
	},
	appendTo : function( parent ) {
		var _parent = parent[0];
		this.forEach( function( child ) {
			if ( child instanceof HTMLElement === true || child.nodeType === 3 || child.nodeType === 11 )
				_parent.appendChild( child );
		});
		return this;
	},
	prepend : function( children ) {
		var parent = this[0];
		children.forEach(function( child ) {
			if ( child instanceof HTMLElement === true || child.nodeType === 3 || child.nodeType === 11 )
				parent.insertBefore( child,  parent.firstElementChild );
		});
		return this;
	},
	remove : function() {
		this.forEach( function( elem ) {
			elem.parentNode.removeChild( elem );
		});
		return;
	},
	parent : function( num ) {
		if ( !num )
			return cQuery( this[0].parentNode );

		var ret = this[0];
		do {
			if ( ret.parentNode )
				ret = ret.parentNode;
			else
				break;
		} while( --num );

		return cQuery( ret );
	},

	text : function( value ) {
		return arguments.length ? this.prop( 'textContent', value ) : this.prop( 'textContent' );
	},
	html : function( value ) {
		return arguments.length ? this.prop( 'innerHTML', value ) : this.prop( 'innerHTML' );
	},
	val : function( value ) {
		return arguments.length ? this.prop( 'value', value ) : this.prop( 'value' );
	}

}; // ${ cQuery.fn } END


cQuery.fn.init.prototype = cQuery.fn;


/**
 * The extend method.
 * @param {Object} base
 * @param {Object=} addition
 */
cQuery.extend = function( base, addition ) {
	'use strict';
	if ( arguments.length === 2 ) {
		for ( var key in addition )
			base[key] = addition[key];
		return base;
	} else {
		addition = base;
		return cQuery.extend( cQuery, addition );
	}
};


// Add static methods to cQuery.
cQuery.extend({

	/**
	 * @param {boolean=} deep
	 */
	noConflict : function( deep ) {
		if ( deep )
			window['cQuery'] = _cQuery;
		window['$'] = _$;
	},

	/**
	 * @return {Array.<*>}
	 */
	makeArray : function( list ) {
		'use strict';
		var ret = [];
		switch ( typeof list ) {
			case 'object' :
				if ( isArray(list) )
					ret = list;
				else if ( typeof list['length'] !== 'undefined' )
					ret = slice.call( list, 0 );
				else
					ret.push( list );
				break;
			default :
				ret.push( list );
				break;
		}
		return ret;
	},

	/**
	 * @param {Array.<*>|Object} elems
	 * @param {function(*)} callback
	 * @return {Array.<*>}
	 */
	map : function( elems, callback ) {
		'use strict';
		var ret = [];

		for ( var i = 0, l = elems.length, value; i < l; ++i ) {
			value = callback( elems[i] );

			if ( value )
				ret.push( value );
		}

		return ret.concat.apply( [], ret );
	},

	forEach : function( arr, callback ) {
		'use strict';
		for ( var i = 0, l = arr.length; i < l; ++i ) {
			callback( arr[i], i );
		}
	},

	// Execute a piece of script in webpage-scripts'
	// context(for content-scripts) or just the global context.
	inject : function inject( func ) {
		func = func.toString();
		cQuery( "<a>" ).attr( 'onclick', "(" + func + ")();" )[0].onclick();
	},

	// Prevent page from loading some resouces by
	// listening to the "beforeload" event in Webkit.
	// Notice: this won't work for requests from plugins.
	blockBySrc : function( arr ) {
		'use strict';

		if ( isArray(arr) ) {
			$( document ).beforeload( function(e) {
				'use strict';
				var t = e.target, src = t.src, p = t.parentNode;
				if ( !src )
					return;
				for ( var s = src.toLowerCase(), i = 0, l = arr.length; i < l; i++ ) {
					if ( s.match( arr[i] ) ) {
						e.preventDefault(); e.stopPropagation();
						p.removeChild( t );
						return;
					}
				}
			}, true);
		} else {
			$( document ).beforeload( function(e) {
				'use strict';
				var t = e.target, src = t.src, p = t.parentNode;
				if ( !src )
					return;
				if ( src.toLowerCase().match(arr) ) {
					e.preventDefault();
					p.removeChild( t );
					return;
				}
			}, true);
		}
	},

	now : function() {
		return new Date().getTime();
	},

	/** @const */
	noop : function(){},

	console : function( msg ) {
		console.log( msg );
	},

	self : function() {
		return this;
	}

});

// Events listeners.
cQuery.forEach( 'click,dblclick,keydown,keyup,mousedown,mouseup,mouseover,mouseout,input,focus,blur,beforeload'.split(','), function( event ) {
	cQuery.fn[event] = function() {
		if ( arguments.length ) {
			var handler = arguments[0], bool = arguments[1] || false;
			this.forEach( function( elem ) {
				elem.addEventListener( event, handler, bool );
			} );
		}
		return this;
	}
});

// The static selector function.
// Returns a pure array of selected DOM elements.
cQuery.find = function( selector, context ) {
	'use strict';
	if ( selector.indexOf( ',' ) > -1 ) {
		return cQuery.map( selector.split( /,\s+/ ), function( single ) {
			return cQuery.find( single, context );
		});
	}

	context = context || document;

	if ( context === document && /^#([\w\-]+)$/.test( selector ) ) {
		var ret = document.getElementById( RegExp.$1 );
		return ret ? [ document.getElementById( RegExp.$1 ) ] : [];
	}

	var ret;

	if ( /^\.([\w\-]+)$/.test( selector ) )
		ret = context.getElementsByClassName( RegExp.$1 );
	else if ( /^\w+$/.test( selector ) || selector === '*' )
		ret = context.getElementsByTagName( selector );
	else if ( selector.indexOf( '\/' ) === -1 )
		ret = context.querySelectorAll( selector );
	else
		return xpath( selector );

	return cQuery.makeArray( ret );
};

/**
 * @param {string} query
 * @return {Array.<HTMLElement>}
 */
function xpath( query ) {
	'use strict';
	var ret = [];
    var xRet = document.evaluate( query, document, null, 6, null );
    for ( var i = xRet.snapshotLength; i--; )
		ret[i] = xRet.snapshotItem( i );
    return ret;
}

// Ajax module.
cQuery['Ajax'] = {
	get : function( url, callback ) {
		'use strict';
		var success;
		if (typeof callback === "function")
			success = callback;
		else
			success = callback.success;
		var XHR = new XMLHttpRequest();
		XHR.open( "GET", url, true );
		XHR.onreadystatechange = function() {
			if ( XHR.readyState === 4 ) {
				if ( XHR.status === 200 || XHR.status === 304 ) {
					success( XHR.responseText );
				} else if ( callback.error ) {
					callback.error( XHR.responseText );
				}
				XHR = null;
			}
		}
		XHR.send( null );
	},
	post : function( url, dataObj, callback ) {
		'use strict';
		var success;
		if ( typeof callback === "function" )
			success = callback;
		else
			success = callback.success;
		var XHR = new XMLHttpRequest();
		var content = '';

		if ( isArray(dataObj) ) {
			dataObj.forEach( function( item ) {
				content += item.name + '=' + item.value + '&';
			});
		} else for ( var key in dataObj ) {
			content += key + '=' + dataObj[key] + '&';
		}

		XHR.open( "POST", url, true );
		XHR.setRequestHeader( "CONTENT-TYPE", "application/x-www-form-urlencoded" );
		XHR.onreadystatechange = function() {
			if ( XHR.readyState === 4 ) {
				if ( XHR.status === 200 ) {
					success( XHR.responseText );
				} else if ( callback.error ) {
					callback.error( XHR.responseText );
				}
				XHR = null;
			}
		}
		XHR.send( content );
	}
};

// File System module.
cQuery['FileSys'] = {

	write : function( name, data, callback ) {
		'use strict';

		callback = callback || {};
		var errorHandler = callback.error || function(e){console.error(e)};
		var onInitFs = function( fs ) {

			fs.root.getFile( name, { create: true, exclusive: false }, function( fileEntry ) {

				fileEntry.createWriter(function( fileWriter ) {

					var bb = new BlobBuilder();
					bb.append( data );
					fileWriter.write( bb.getBlob( 'text/plain' ) );

				}, errorHandler );

			}, errorHandler );
		}

		requestFileSystem( window.PERSISTENT, 1024*1024, onInitFs, errorHandler );
	},

	read : function( name, callback ) {
		'use strict';

		callback = callback || {};
		var errorHandler = callback.error || function(e){console.error(e)};
		var successHandler = callback.success || function(){console.log('File read successed.')};
		var onInitFs = function( fs ) {

			fs.root.getFile( name, {}, function( fileEntry ) {

				fileEntry.file( function( file ) {
					var reader = new FileReader();

					reader.onloadend = function( e ) {
						successHandler( reader.result );
					};

					reader.readAsText( file );
				}, errorHandler );

			}, errorHandler );
		}

		requestFileSystem( window.PERSISTENT, 1024*1024, onInitFs, errorHandler );
	},
	remove : function( name ) {
		window.requestFileSystem( window.PERSISTENT, 1024*1024, function( fs ) {
		  fs.root.getFile( name, { create: false }, function( fileEntry ) {

			fileEntry.remove( function() {
			  console.log( 'File removed.' );
			});

		  });
		});
	}
}; // ${ FileSys } END

//@TODO: Dialog module.
cQuery['Dialog'] = {
	alert : function( content, callback ) {

	},
	confirm : null,
	prompt : null
}; // ${ Dialog } END

/**
 * @see https://trac.webkit.org/wiki/PrefixedAPIs
 */
cQuery['HTML5Compat'] = function() {
	window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
	window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
	window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder;
};

// Event module.
/** @type {Array.<funtion(!Object)>} */
var _EventDispatcher = [];
cQuery.extend({
	addEventHandler : function( eventName, func ) {
		_EventDispatcher.push( {
			'eventName' : eventName,
			'handler' : func
		} );
	},
	fireEvent : function( eventName, info, crossPage ) {
		'use strict';
		for ( var i = 0, l = _EventDispatcher.length; i < l; i++ ) {
			if ( _EventDispatcher[i]['eventName'] === eventName ) {
				_EventDispatcher[i].handler( info );
			}
		}

		if ( crossPage ) {
			chrome.extension.sendRequest( {'event':eventName, 'info':info} );
		}
	}
});

/**
 * Chrome extension APIs.
 */
(function( $ ) {
	'use strict';

	var rChrome = window.chrome;
	var chrome = {
		details : rChrome.app.getDetails(), // would be {null} if it's in a content-script
		getURL : rChrome.extension.getURL,
		onRequest : function( msg, sender, Response ) {
			rChrome.extension.onRequest.addListener( msg, sender, Response );
		},
		sendRequest : rChrome.extension.sendRequest
	};
	chrome.background = chrome.details && typeof chrome.details.app === 'undefined';

	// If it's in background, add more methodes.
	if ( chrome.background ) {
		$.extend( chrome, {
			hasPerm : rChrome.permissions.contains,
			requestPerm : rChrome.permissions.request
		});

		if ( rChrome.contextMenus ) {
			$.extend( chrome, {
				addMenu : rChrome.contextMenus.create,
				removeAllMenus : rChrome.contextMenus.removeAll
			} );
		}
	}

	// Expose.
	$.chrome = chrome;

})( cQuery );


window['cQuery'] = window['$'] = cQuery;


})(); // { cQuery library } END


/**
 *  @description  A little preparation for BDB.
 */

/** @const */
var DEBUG = true;

/** @const */
var log = DEBUG ? $.console : $.noop;


$.HTML5Compat();
$c = $.chrome;

